# Copyright 2011 Google Inc. All Rights Reserved.
import cgi
import time
import google.appengine.ext.webapp.util
import hashlib
import logging
import os
import urlparse
import urllib
import glp.util
import glp.users
from django.utils import simplejson
from google.appengine.api import mail
from google.appengine.api import users
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from glp.django.forms import RegistrationForm
from glp.django import forms

""" This module manages the user login process.

The regular registration process is:
  login page -> registration form (RegisterFormRenderer)
     -> prompt for email (RegisterUserRenderer) -> login
     -> optional form (SaveOptionalForm)

The regular signin process is:
  login page -> prompt for email (RegisterUserRenderer) -> login

Each step is explained below:
 - login page: user chooses whether to register or sign in.
 - registration form: user fills out required registration details.
 - prompt for email: user chooses which email type they have.
 - login: user logs in via login link (which uses OpenID).
 - optional form: user fills out optional registration details.
"""

__author__ = 'alyssad@google.com (Alyssa Daw)'

_NEW_GMAIL_URL = 'http://mail.google.com/mail/signup?continue=%s'
_DEBUG = True


class RegisterFormRenderer(webapp.RequestHandler):

  def get(self):
    """Render the registration form."""
    user = users.get_current_user()
    if user:
      self.redirect(users.create_logout_url(self.request.uri))
    else:
      # Render the registration form (normal case)
      context = { 'registration_form' : forms.RegistrationForm(),
                  'post_url' : '/users/register2.html' }
      self.response.out.write(template.render(
          'templates/register.html', context, debug=_DEBUG))


class RegisterUserRenderer(webapp.RequestHandler):

  def _GetUrlFromForm(self, form):
    """Convert form data to a url with query strings."""
    # TODO(alyssad): Make this work for forms with multiple values.
    bits = []
    for item in form:
      bits.append('%s=%s' % (item, form.get(item)))
    url = '&'.join(bits)
    # TODO(sdd): quote this
    return url

  def post(self):
    """Verify the registration form and prompt the user to sign in."""
    form = forms.RegistrationForm(data=self.request.POST)
    if form.is_valid():
      # Have them validate by making a Google account.
      form_data = self._GetUrlFromForm(self.request.POST)
      user_data = ('{ "gmail_url" : "%s",'
                   '  "none_url" : "%s",'
                   '  "yahoo_url" : "%s",'
                   '  "aol_url" : "%s" }' %
                   (users.create_login_url(
                        '/users/register4.html?%s' % form_data),
                    '/users/gmail_guidance.html?%s' % form_data,
                    '/users/yahoo_guidance.html?%s' % form_data,
                    '/users/aol_guidance.html?%s' % form_data))
      context = { 'user_data' : user_data }
      self.response.out.write(template.render(
          'templates/signin_openid.html', context, debug=_DEBUG))
    else:
      # The form was invalid, so show the form again.
      context = { 'registration_form' : form , 'post_url' : self.request.uri }
      self.response.out.write(template.render(
            'templates/register.html', context, debug=_DEBUG))


class SignIn(webapp.RequestHandler):

  def get(self, user_type):
    """If the user is signed in, take them to the home page.

    If they are not signed in, take them back to the login page.
    """
    user = glp.users.get_current_user()
    if user:
      context = { 'user_email' : user.email,
                  'post_url' : '/save_optional' }
      self.response.out.write(template.render(
          'templates/signed_in_final_%s.html' % user_type, context, debug=_DEBUG))
    else:
      self.redirect('/users/login.html')

  def post(self, user_type):
    self.get(user_type)


class SaveOptionalForm(webapp.RequestHandler):

  def post(self):
    user = glp.users.get_current_user()
    levels = self.request.get_all('level')
    if 'primary' in levels:
      user.primary = self.request.get_all('primary')
    if 'ordinary' in levels:
      user.ordinary = self.request.get_all('ordinary')
    if 'advanced' in levels:
      user.advanced = self.request.get_all('advanced')
    if 'ttc' in levels:
      user.teacher_training = self.request.get_all('ttc')
    user.put()
    self.redirect('/users/register_success_subjects.html')


class ExistingUserSignin(webapp.RequestHandler):

  def get(self):
    """Have the user log in via OpenID."""
    user = glp.users.get_current_user()
    if user:
      self.redirect(glp.users.create_logout_url(self.request.uri))
    else:
      context = { 'user_type' : 'existing' }
      self.response.out.write(template.render(
          'templates/signin_openid.html', context, debug=_DEBUG))

  def post(self):
    self.get()


class Login(webapp.RequestHandler):

  def post(self, user_type):
    form_as_url = self._GetUrlFromForm(self.request.POST)
    federated_identity = self.request.get('domain')

    if not federated_identity:
      self._ReRenderIdentity()
      return

    if federated_identity == 'no_email':
      form = forms.RegistrationForm(self.request.POST)
      context = { 'registration_form' : form }
      self.response.out.write(template.render(
          'templates/gmail_account.html', context, debug=_DEBUG))
      return

    login_url = glp.users.create_login_url(
        '/finish/%s/?%s' % (user_type, form_as_url),
        federated_identity=federated_identity)
    self.redirect(login_url)

  def _ReRenderIdentity(self):
    # Have them register with OpenID (normal case).
    context = { 'registration_form' : forms.RegistrationForm(self.request.POST),
                'user_type' : 'new'}
    self.response.out.write(template.render(
        'templates/signin_openid.html', context, debug=_DEBUG))

  def _GetUrlFromForm(self, form):
    """Convert form data to a url with query strings."""
    # TODO(alyssad): Make this work for forms with multiple values.
    url = ''
    for item in form:
      url = '%s&%s=%s' % (url, item, form.get(item))
    # TODO(sdd): quote this
    return url


class Logout(webapp.RequestHandler):

  def get(self):
    redirect_url = self.request.get('return')
    glp.users.LogoutCurrentUser()
    self.redirect(redirect_url)

  def post(self):
    self.get()


class UserInfoHandler(webapp.RequestHandler):

  def get(self):
    next_url = self.request.get('requester')
    user = glp.users.get_current_user()
    if not user:
      response = {
        'logout_url' : None,
        'email' : None,
        'first_name' : None,
        'last_name' : None
        }
    else:
      response = {
        'logout_url' : glp.users.create_logout_url(next_url),
        'email' : user.email,
        'first_name' : user.first_name,
        'last_name' : user.last_name
        }
    self.response.out.write(simplejson.dumps(response))


# OLD CODE
class RenderPage(webapp.RequestHandler):
    def get(self):
        if self.request.path == '/dynamic/login-page.html':
            self.HandleLogin()
        elif self.request.path == '/dynamic/login-box.html':
            self.RenderLoginBox()
        elif self.request.path == '/dynamic/request-registration-confirmation.html':
            self.RenderConfirmationRequest()
        elif self.request.path == '/dynamic/user-dump.html':
            self.RenderUserDump()
        else:
            raise NotImplementedError(self.request.path)

    def post(self):
        if self.request.path == '/dynamic/login-page.html':
            self.HandleLogin()
        else:
            raise NotImplementedError(self.request.path)

    def RenderUserDump(self):
        writer = glp.util.ModelDumper(['email', 'first_name', 'last_name', 'school', 'role', 'confirmed', 'created', 'modified', 'registration_id'])
        for user in glp.users.GetUsers():
            writer.add(user)
        glp.util.SimplePage(self.response, writer.render())

    def RenderRegister(self):
        user = users.get_current_user()
        context = {
                'user_email' : user.email(),
                'logout_url' : users.create_logout_url(self.request.uri)
                }
        if glp.users.GetPendingRegistration(user.email()):
            # TODO(sdd): Handle "lost confirmation e-mail"
            self.response.out.write(template.render(
                    "templates/registered_already.html", context))
            return
        if glp.users.GetUser(user.email()):
            # TODO(sdd): Handle "lost confirmation e-mail"
            self.response.out.write(template.render(
                    "templates/registered_already.html", context))
            return
        parent_bits = urlparse.urlparse(self.request.get('parent'))
        if self.request.environ['REQUEST_METHOD'] == 'GET':
            context['registration_form'] = RegistrationForm()
            context['registration_id'] = hashlib.sha1("%s-%s-%d" % (user.email(), self.request.remote_addr, time.time())).hexdigest()
        elif self.request.environ['REQUEST_METHOD'] != 'POST':
            X
        else:
            form = RegistrationForm(data=self.request.POST)
            if form.is_valid():
                registration = form.save(commit=False)
                registration.email = users.get_current_user().email()
                registration.registration_id = self.request.get('registration_id')
                registration.confirmed = False
                registration.put()
                registration_name = '%s %s' % (registration.first_name, registration.last_name)
                context = {
                    'registration_name' : registration_name,
                    'registration_email' : registration.email,
                    'confirm_url' : '%s://%s/confirm-registration?id=%s' % (parent_bits.scheme, parent_bits.hostname, registration.registration_id)
                    }
                mail_text = template.render("templates/confirm-registration.txt", context)
                mail.send_mail(sender="Rwanda Education Commons Registration Robot <noreply@educationcommons.rw>",
                               to="%s <%s>" % (registration_name, registration.email),
                               subject="Rwanda Education Commons Registration Confirmation",
                               body=mail_text)
                self.redirect('/dynamic/request-registration-confirmation.html?id=%s&parent=%s://%s' % (registration.email, parent_bits.scheme, parent_bits.hostname))
                return
            else:
                context['registration_form'] = form
                context['registration_id'] = self.request.get('registration_id')
        context['post_url'] = '%s?parent=%s://%s' % (self.request.uri, parent_bits.scheme, parent_bits.hostname)
        self.response.out.write(template.render(
                "templates/register.html", context, True))

    def RenderConfirmationRequest(self):
        form_email = self.request.get('id')
        if form_email == None:
            self.redirect('/dynamic/login-page.html')
            return
        registration = glp.users.GetPendingRegistration(form_email)
        if not registration:
            self.redirect('/dynamic/login-page.html')
            return
        self.response.out.write(template.render(
                "templates/confirm-registration.html",
                { 'registration_email' : registration.email }))

    def RenderConfirmation(self):
        parent_bits = urlparse.urlparse(self.request.get('parent'))
        id = cgi.parse_qs(parent_bits.query)['id'][0]
        if not id:
            self.redirect('/dynamic/login-page.html')
            return
        registration = glp.users.ConfirmRegistration(id)
        if not registration:
            self.redirect('/dynamic/login-page.html')
            return
        glp.util.EnqueueTask('users', 'update-membership', registration.email, 'user-manager')
        post_confirm_url = '%s://%s' % (parent_bits.scheme, parent_bits.hostname)
        self.response.out.write(template.render(
                "templates/registration-confirmed.html", {'post_confirm_url' : post_confirm_url }))

    def RenderLoginBox(self):
        user = users.get_current_user()
        glp_user = glp.users.GetUser(user.email()) if user else None
        self.response.out.write(template.render(
                "templates/login-box.html", {
                    'email' : user.email() if user else None,
                    'registered' : True if glp_user else False,
                    'logout_url' :users.create_logout_url(self.request.get('parent'))}))

    def HandleLogin(self):
        user = users.get_current_user()
        glp_user = glp.users.GetUser(user.email()) if user else None
        parent_bits = urlparse.urlparse(self.request.get('parent'))
        if self.request.get('up_mode') == 'confirm':
            self.RenderConfirmation()
        elif self.request.get('up_mode') == 'register':
            if user:
                self.RenderRegister()
            else:
                login_url = users.create_login_url(self.request.uri)
                self.redirect(login_url)
        elif self.request.get('mode') == 'restart' or not self.request.get('mode'):
            gadget_bits = urlparse.urlparse(self.request.url)
            redirect_url = '%s://%s/dynamic/login-page.html?mode=redirect&parent=%s://%s' % (gadget_bits.scheme, gadget_bits.hostname, parent_bits.scheme, parent_bits.hostname)
            login_url = users.create_login_url(redirect_url)
            if user:
                logout_url = users.create_logout_url(login_url)
                self.redirect(logout_url)
            else:
                self.redirect(login_url)
        else:
            parent_prefix = '%s://%s' % (parent_bits.scheme, parent_bits.hostname)
            if glp_user:
                redirect_url = parent_prefix
            elif user:
                redirect_url = parent_prefix + '/register'
            else:
                redirect_url = users.create_login_url(self.request.uri)
            self.response.out.write(template.render(
                    "templates/redirect.html", {'redirect_url' : redirect_url}))


class EnqueueTask(webapp.RequestHandler):
    def get(self):
        tasks = []
        if self.request.path == '/admin/users/rebuild-memberships':
            email = self.request.get('id')
            if email:
                tasks.append(glp.util.EnqueueTask('users', 'update-membership', email, 'user-manager'))
            else:
                authenticator = glp.util.ClientLoginAuthenticator()
                moves = glp.users.ComputeGroupRebuildingTasks(authenticator)
                if moves == None: return
                for move in moves:
                    glp.util.EnqueueDictTask('users', '%s-member' % move[0], {
                            'member_id' : move[1],
                            'group_id' : move[2]}, 'user-manager')
                    tasks.append(move[3])
        else:
            raise NotImplementedError(self.request.path)
        message = ''.join(['%s enqueued.\n' % task for task in tasks])
        glp.util.SimplePage(self.response, message)

class ExecuteTask(webapp.RequestHandler):
    def post(self):
        authenticator = glp.util.ClientLoginAuthenticator()
        if self.request.path == '/tasks/users/update-membership':
            email = self.request.body
            if not email: raise AttributeException('no e-mail specified for membership update')
            if not glp.users.UpdateGroups(authenticator, email):
                logging.info('need session token before updating groups for %s' % email)
        elif self.request.path == '/tasks/users/add-member':
            if not glp.users.AddUserToGroup(authenticator,
                                            self.request.POST.get('member_id'),
                                            self.request.POST.get('group_id')):
                logging.info('need session token before adding %s to %s' % (
                        self.request.POST.get('member_id'),
                        self.request.POST.get('group_id')))
        elif self.request.path == '/tasks/users/remove-member':
            if not glp.users.RemoveUserFromGroup(authenticator,
                                                 self.request.POST.get('member_id'),
                                                 self.request.POST.get('group_id')):
                logging.info('need session token before removing %s from %s' % (
                        self.request.POST.get('member_id'),
                        self.request.POST.get('group_id')))

        else:
            raise NotImplementedError(self.request.path)

class Query(webapp.RequestHandler):
    def get(self):
        if self.request.path == '/admin/users/user':
            user = users.get_current_user()
            email = user.email() if user else None
            if not user:
                state = 'unauthenticated'
            elif glp.users.GetUser(user.email()):
                state = 'registered'
            elif glp.users.GetPendingRegistration(user.email()):
                state = 'unconfirmed'
            else:
                state = 'authenticated'
            self.response.out.write(simplejson.dumps({
                        'email' : email,
                        'state' : state }))
        else:
            raise NotImplementedError(self.request.path)


class Query(webapp.RequestHandler):
    def get(self):
        return self.response.out.write(users.create_login_url("http://www.google.com"))

class HeaderHandler(webapp.RequestHandler):
    def get(self):
        user = users.get_current_user()
        if user:
          user_email = user.email()
          logout_url = users.create_logout_url(
            'http://www.educationcommons.rw');
        else:
          user_email = ''
          logout_url = users.create_logout_url('/users/signin.html')

        context = {
          'user_info' :
            ("{ 'logout_url' : '%s', 'user_email' : '%s' }" %
             (logout_url, user_email))
          }
        self.response.out.write(template.render(
            'templates/header.html', context, debug=_DEBUG))


class CheckRegisteredHandler(webapp.RequestHandler):
  def get(self):
    user = users.get_current_user()
    assert user
    glp_user = glp.users.GetUser(user.email())
    if glp_user:
      self.response.out.write(template.render(
          'templates/signin2.html', {}, debug=_DEBUG))
    else:
      context = { 'user_email' : user.email() }
      self.response.out.write(template.render(
          'templates/not_registered.html', context, debug=_DEBUG))

class SignInHandler(webapp.RequestHandler):
  def get(self):
    context = {
      'login_url' : users.create_login_url('/users/check_registered.html')
    }
    self.response.out.write(template.render(
        'templates/signin.html', context, debug=_DEBUG))


class Register3Renderer(webapp.RequestHandler):

  def post(self):
    form = forms.RegistrationForm(self.request.POST)
    context = { 'registration_form' : form }
    self.response.out.write(template.render(
        'templates/register3.html', context, debug=_DEBUG))

class Register4Renderer(webapp.RequestHandler):

  def get(self):
    form = forms.RegistrationForm(data=self.request)
    if form.is_valid():
      if self._CheckRegisteredAndSave(form):
        self.redirect('/users/register5.html')
    else:
      # The form was invalid, so show the form again.
      context = { 'registration_form' : form, 'post_url' : self.request.uri }
      self.response.out.write(template.render(
          'templates/register.html', context, debug=_DEBUG))

  def _CheckRegisteredAndSave(self, form):
    user = users.get_current_user()
    glp_user = glp.users.GetUser(user.email())
    if not glp_user:
      # Save the user (normal case).
      registration = form.save(commit=False)
      registration.email = user.email()
      registration.confirmed = True
      registration.put()
      return True
    else:
      # The current user has already registered so sign them in and show
      # already_registered.
      context = { 'user_email' : user.email() }
      self.response.out.write(template.render(
          'templates/registered_already.html', context, debug=_DEBUG))
      return False


class Register6Renderer(webapp.RequestHandler):

  def post(self):
    user = users.get_current_user()
    glp_user = glp.users.GetUser(user.email())
    levels = self.request.get_all('level')
    if 'primary' in levels:
      glp_user.primary = self.request.get_all('primary')
    if 'ordinary' in levels:
      glp_user.ordinary = self.request.get_all('ordinary')
    if 'advanced' in levels:
      glp_user.advanced = self.request.get_all('advanced')
    if 'ttc' in levels:
      glp_user.teacher_training = self.request.get_all('ttc')
    glp_user.put()
    self.redirect('/gadgets/register7.html')

class GuidanceRenderer(webapp.RequestHandler):

  def get(self, guidance_type):
    query = urlparse.urlparse(self.request.uri).query
    context = { 'post_action' : users.create_login_url(
                                    '/users/register4.html?%s' % query) }
    self.response.out.write(template.render(
        'templates/%s_guidance.html' % guidance_type, context, debug=_DEBUG))

def main():
    # logging.getLogger().setLevel(logging.DEBUG)
    application = webapp.WSGIApplication([
            # TODO(alyssad): combine register + register2
            ('/continue_register/(.*)/.*', Login),
            ('/signin/(.*)', SignIn),
            ('/users/login2.html.*', ExistingUserSignin),
            ('/save_optional', SaveOptionalForm),
            ('/continue_register.*', Login),
            ('/users/logout.*', Logout),
            ('/users/user_info', UserInfoHandler),

            ('/dynamic/login-page.html', RenderPage),
            ('/dynamic/login-box.html', RenderPage),
            ('/dynamic/request-registration-confirmation.html', RenderPage),
            ('/dynamic/user-manager.html', RenderPage),
            ('/dynamic/user-dump.html', RenderPage),

            ('/users/login-url', Query),

            ('/admin/users/rebuild-memberships', EnqueueTask),
            ('/admin/users/remove-member', EnqueueTask),
            ('/admin/users/rebuild-member', EnqueueTask),

            ('/tasks/users/update-membership', ExecuteTask),
            ('/tasks/users/add-member', ExecuteTask),
            ('/tasks/users/remove-member', ExecuteTask),

            ('/admin/users/user', Query),

            ('/gadgets/header.html', HeaderHandler),
            ('/users/signin.html', SignInHandler),
            ('/users/register.html', RegisterFormRenderer),
            ('/users/register2.html', RegisterUserRenderer),
            ('/users/register3.html', Register3Renderer),
            ('/users/register4.html', Register4Renderer),
            ('/users/register6.html', Register6Renderer),
            ('/users/(.*)_guidance.html', GuidanceRenderer),
            ('/users/check_registered.html', CheckRegisteredHandler)

            ], debug=True)
    google.appengine.ext.webapp.util.run_wsgi_app(application)

if __name__ == "__main__":
    main()
